/*!	 valuenode_dynamiclist.cpp
**	 Implementation of the "Dynamic List" valuenode conversion.
**
**	Copyright (c) 2002-2005 Robert B. Quattlebaum Jr., Adrian Bentley
**	Copyright (c) 2008 Chris Moore
**  Copyright (c) 2011 Carlos López
**
**	This package is free software; you can redistribute it and/or
**	modify it under the terms of the GNU General Public License as
**	published by the Free Software Foundation; either version 2 of
**	the License, or (at your option) any later version.
**
**	This package is distributed in the hope that it will be useful,
**	but WITHOUT ANY WARRANTY; without even the implied warranty of
**	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
**	General Public License for more details.
**
*/

#ifdef USING_PCH
#	include "pch.h"
#else
#ifdef HAVE_CONFIG_H
#	include <config.h>
#endif

#include "valuenode_dynamiclist.h"
#include "valuenode_const.h"
#include "valuenode_composite.h"
#include <synfig/general.h>
#include <synfig/localization.h>
#include <synfig/valuenode_registry.h>
#include <synfig/exception.h>
#include <vector>
#include <list>
#include <algorithm>
#include <synfig/canvas.h>

#endif

using namespace std;
using namespace etl;
using namespace synfig;

REGISTER_VALUENODE(ValueNode_DynamicList, RELEASE_VERSION_0_61_06, "dynamic_list", "Dynamic List")

ValueNode_DynamicList::ListEntry::ListEntry():
    index(0)
{
}

ValueNode_DynamicList::ListEntry::ListEntry(const ValueNode::Handle &value_node):
    value_node(value_node),
    index(0)
{
}

ValueNode_DynamicList::ListEntry::ListEntry(const ValueNode::Handle &value_node, Time begin, Time end):
    value_node(value_node)
{
    add(begin, false);
    add(end, false);
    add((begin + end) * 0.5, true);
}

ValueNode_DynamicList::ListEntry::ActivepointList::iterator
ValueNode_DynamicList::ListEntry::add(Time time, bool status, int priority)
{
    typedef synfig::ValueNode_DynamicList::ListEntry::ActivepointList::iterator iterator;

    // \optimize
    Activepoint ap(time, status, priority);
    ap.set_parent_index(get_index());
    ap.set_parent_value_node(get_parent_value_node());
    timing_info.push_back(ap);
    iterator iter(--iterator(timing_info.end()));
    timing_info.sort();

    return iter;
}

ValueNode_DynamicList::ListEntry::ActivepointList::iterator
ValueNode_DynamicList::ListEntry::add(const Activepoint &x)
{
    typedef synfig::ValueNode_DynamicList::ListEntry::ActivepointList::iterator iterator;

    // \optimize
    Activepoint ap(x);
    ap.set_parent_index(get_index());
    ap.set_parent_value_node(get_parent_value_node());
    timing_info.push_back(ap);
    iterator iter(--iterator(timing_info.end()));
    timing_info.sort();

    return iter;
}

void
ValueNode_DynamicList::reindex()
{
    int i(0);

    std::vector<ListEntry>::iterator iter;

    for (iter = list.begin(); iter != list.end(); ++iter) {
        assert(iter->value_node);

        if (iter->index != i || iter->get_parent_value_node().get() != this) {
            ActivepointList::iterator iter2;

            if (iter->timing_info.size()) // is this line really necessary?
                for (iter2 = iter->timing_info.begin(); iter2 != iter->timing_info.end(); ++iter2) {
                    iter2->set_parent_index(i);
                    iter2->set_parent_value_node(this);
                }

            iter->index = i;
            iter->set_parent_value_node(this);
        }
    }
}

ValueNode_DynamicList::ListEntry
ValueNode_DynamicList::create_list_entry(int index, Time time, Real origin)
{
    ValueNode_DynamicList::ListEntry ret;
    int c(link_count());
    synfig::ValueBase prev, next;

    if (c) {
        index = index % c;
    } else {
        index = 0;
    }

    assert(index >= 0);

    ret.index = index;
    ret.set_parent_value_node(this);

    if (c) {
        next = (*list[index].value_node)(time);

        if (index != 0) {
            prev = (*list[index - 1].value_node)(time);
        } else {
            if (get_loop()) {
                prev = (*list[link_count() - 1].value_node)(time);
            } else {
                prev = next;
            }
        }
    }

    Type &type(get_contained_type());

    if (type == type_vector) {
        if (c) {
            Vector a(prev.get(Vector())), b(next.get(Vector()));
            ret.value_node = ValueNode_Const::create((b - a) * origin + a);
        } else {
            ret.value_node = ValueNode_Const::create(Vector());
        }
    } else if (type == type_real) {
        if (c) {
            Real a(prev.get(Real())), b(next.get(Real()));
            ret.value_node = ValueNode_Const::create((b - a) * origin + a);
        } else {
            ret.value_node = ValueNode_Const::create(Real());
        }
    } else if (type == type_color) {
        if (c) {
            Color a(prev.get(Color())), b(next.get(Color()));
            ret.value_node = ValueNode_Composite::create((b - a) * origin + a);
        } else {
            ret.value_node = ValueNode_Const::create(Color());
        }
    } else if (type == type_angle) {
        if (c) {
            Angle a(prev.get(Angle())), b(next.get(Angle()));
            ret.value_node = ValueNode_Const::create((b - a) * origin + a);
        } else {
            ret.value_node = ValueNode_Const::create(Angle());
        }
    } else if (type == type_time) {
        if (c) {
            Time a(prev.get(Time())), b(next.get(Time()));
            ret.value_node = ValueNode_Const::create((b - a) * origin + a);
        } else {
            ret.value_node = ValueNode_Const::create(Time());
        }
    } else {
        ret.value_node = ValueNode_Const::create(get_contained_type());
    }

    ret.value_node->set_parent_canvas(get_parent_canvas());
    return ret;
}

ValueNode_DynamicList::ListEntry::ActivepointList::iterator
ValueNode_DynamicList::ListEntry::find(const UniqueID& x)
{
    return std::find(timing_info.begin(), timing_info.end(), x);
}

ValueNode_DynamicList::ListEntry::ActivepointList::const_iterator
ValueNode_DynamicList::ListEntry::find(const UniqueID& x)const
{
    return std::find(timing_info.begin(), timing_info.end(), x);
}

void
ValueNode_DynamicList::ListEntry::erase(const UniqueID& x)
{
    timing_info.erase(find(x));
}

ValueNode_DynamicList::ListEntry::ActivepointList::iterator
ValueNode_DynamicList::ListEntry::find(const Time& x)
{
    typedef synfig::ValueNode_DynamicList::ListEntry::ActivepointList ActivepointList;

    ActivepointList::iterator iter;

    for (iter = timing_info.begin(); iter != timing_info.end(); ++iter)
        if (iter->time == x) {
            return iter;
        }

    throw Exception::NotFound("ValueNode_DynamicList::ListEntry::find():" + x.get_string());
}

ValueNode_DynamicList::ListEntry::ActivepointList::const_iterator
ValueNode_DynamicList::ListEntry::find(const Time& x)const
{
    typedef synfig::ValueNode_DynamicList::ListEntry::ActivepointList ActivepointList;

    ActivepointList::const_iterator iter;

    for (iter = timing_info.begin(); iter != timing_info.end(); ++iter)
        if (iter->time == x) {
            return iter;
        }

    throw Exception::NotFound("ValueNode_DynamicList::ListEntry::find()const:" + x.get_string());
}

ValueNode_DynamicList::ListEntry::ActivepointList::iterator
ValueNode_DynamicList::ListEntry::find_next(const Time& x)
{
    typedef synfig::ValueNode_DynamicList::ListEntry::ActivepointList ActivepointList;

    ActivepointList::iterator iter;

    for (iter = timing_info.begin(); iter != timing_info.end(); ++iter)
        if (iter->time > x) {
            return iter;
        }

    throw Exception::NotFound("ValueNode_DynamicList::ListEntry::find_next():" + x.get_string());
}

ValueNode_DynamicList::ListEntry::ActivepointList::const_iterator
ValueNode_DynamicList::ListEntry::find_next(const Time& x)const
{
    typedef synfig::ValueNode_DynamicList::ListEntry::ActivepointList ActivepointList;

    ActivepointList::const_iterator iter;

    for (iter = timing_info.begin(); iter != timing_info.end(); ++iter)
        if (iter->time > x) {
            return iter;
        }

    throw Exception::NotFound("ValueNode_DynamicList::ListEntry::find_next()const:" + x.get_string());
}

ValueNode_DynamicList::ListEntry::ActivepointList::iterator
ValueNode_DynamicList::ListEntry::find_prev(const Time& x)
{
    typedef synfig::ValueNode_DynamicList::ListEntry::ActivepointList ActivepointList;

    ActivepointList::iterator iter;
    iter = timing_info.end();

    do {
        --iter;

        if (iter->time < x) {
            return iter;
        }
    } while (iter != timing_info.begin());

    throw Exception::NotFound("ValueNode_DynamicList::ListEntry::find_prev():" + x.get_string());
}

ValueNode_DynamicList::ListEntry::ActivepointList::const_iterator
ValueNode_DynamicList::ListEntry::find_prev(const Time& x)const
{
    typedef synfig::ValueNode_DynamicList::ListEntry::ActivepointList ActivepointList;

    ActivepointList::const_iterator iter;
    iter = timing_info.end();

    do {
        --iter;

        if (iter->time < x) {
            return iter;
        }
    } while (iter != timing_info.begin());

    throw Exception::NotFound("ValueNode_DynamicList::ListEntry::find_prev()const:" + x.get_string());
}

int
ValueNode_DynamicList::ListEntry::find(const Time& begin, const Time& end, std::vector<Activepoint*>& selected)
{
    Time curr_time(begin);
    int ret(0);

    // try to grab first waypoint
    try {
        ActivepointList::iterator iter;
        iter = find(curr_time);
        selected.push_back(&*iter);
        ret++;
    } catch (...) { }

    try {
        ActivepointList::iterator iter;

        while (true) {
            iter = find_next(curr_time);
            curr_time = iter->get_time();

            if (curr_time >= end) {
                break;
            }

            selected.push_back(&*iter);
            ret++;
        }
    } catch (...) { }

    return ret;
}

float
ValueNode_DynamicList::ListEntry::amount_at_time(const Time &t, bool *rising)const
{
    typedef synfig::ValueNode_DynamicList::ListEntry::ActivepointList ActivepointList;

    if (timing_info.empty()) {
        return 1.0f;
    }

    try {
        ActivepointList::const_iterator iter;
        iter = find(t);
        return iter->state ? 1.0f : 0.0f;
    } catch (...) { }

    ActivepointList::const_iterator prev_iter;
    ActivepointList::const_iterator next_iter;

    try	{
        prev_iter = find_prev(t);
    } catch (...) {
        return find_next(t)->state ? 1.0f : 0.0f;
    }

    try	{
        next_iter = find_next(t);
    } catch (...) {
        return prev_iter->state ? 1.0f : 0.0f;
    }

    if (next_iter->state == prev_iter->state) {
        return next_iter->state ? 1.0f : 0.0f;
    }

    if (rising) {
        *rising = next_iter->state;
    }

    if (next_iter->state == true) {
        return float((t - prev_iter->time) / (next_iter->time - prev_iter->time));
    }

    return float((next_iter->time - t) / (next_iter->time - prev_iter->time));
}

Activepoint
ValueNode_DynamicList::ListEntry::new_activepoint_at_time(const Time& time)const
{
    Activepoint activepoint;

    activepoint.set_state(status_at_time(time));
    activepoint.set_priority(0);

    return activepoint;
}

bool
ValueNode_DynamicList::ListEntry::status_at_time(const Time &t)const
{
    // typedef synfig::ValueNode_DynamicList::ListEntry::Activepoint Activepoint;
    typedef synfig::ValueNode_DynamicList::ListEntry::ActivepointList ActivepointList;

    ActivepointList::const_iterator entry_iter;
    ActivepointList::const_iterator prev_iter;
    bool state(true);

    // New "symmetric" state mechanism
    if (!timing_info.empty()) {
        if (timing_info.size() == 1) {
            state = timing_info.front().state;
        } else {
            // \optimize Perhaps we should use a binary search...?
            // This will give us the first activepoint that is after t.
            for (entry_iter = timing_info.begin(); entry_iter != timing_info.end(); ++entry_iter) {
                if (entry_iter->time == t) {
                    // If we hit the entry right on the nose, then we don't
                    // have to do anything more
                    return entry_iter->state;
                }

                if (entry_iter->time > t) {
                    break;
                }

            }

            prev_iter = entry_iter;
            prev_iter--;

            //
            //	   prev_iter^		^entry_iter

            if (entry_iter == timing_info.end()) {
                state = prev_iter->state;
            } else if (entry_iter == timing_info.begin()) {
                state = entry_iter->state;
            } else if (entry_iter->priority == prev_iter->priority) {
                state = entry_iter->state || prev_iter->state;
            } else if (entry_iter->priority > prev_iter->priority) {
                state = entry_iter->state;
            } else {
                state = prev_iter->state;
            }
        }
    }

    return state;
}

void
ValueNode_DynamicList::add(const ValueNode::Handle &value_node, int index)
{
    ListEntry list_entry(value_node);
    list_entry.timing_info.size();

    if (index < 0 || index >= (int)list.size()) {
        list.push_back(list_entry);
    } else {
        list.insert(list.begin() + index, list_entry);
    }

    add_child(value_node.get());
    reindex();

    if (get_parent_canvas()) {
        get_parent_canvas()->signal_value_node_child_added()(this, value_node);
    } else if (get_root_canvas() && get_parent_canvas()) {
        get_root_canvas()->signal_value_node_child_added()(this, value_node);
    }
}

void
ValueNode_DynamicList::add(const ListEntry &list_entry, int index)
{
    if (index < 0 || index >= (int)list.size()) {
        list.push_back(list_entry);
    } else {
        list.insert(list.begin() + index, list_entry);
    }

    add_child(list_entry.value_node.get());

    reindex();

    if (get_parent_canvas()) {
        get_parent_canvas()->signal_value_node_child_added()(this, list_entry.value_node);
    } else if (get_root_canvas() && get_parent_canvas()) {
        get_root_canvas()->signal_value_node_child_added()(this, list_entry.value_node);
    }
}

void
ValueNode_DynamicList::erase(const ValueNode::Handle &value_node_)
{
    ValueNode::Handle value_node(value_node_);

    assert(value_node);

    if (!value_node) {
        throw String("ValueNode_DynamicList::erase(): Passed bad value node");
    }

    std::vector<ListEntry>::iterator iter;

    for (iter = list.begin(); iter != list.end(); ++iter)
        if (iter->value_node == value_node) {
            list.erase(iter);

            if (value_node) {
                remove_child(value_node.get());
                // changed to fix bug 1420091 - it seems that when a .sif file containing a bline layer encapsulated inside
                // another layer, get_parent_canvas() is false and get_root_canvas() is true, but when we come to erase a
                // vertex, both are true.  So the signal is sent to the parent, but the signal wasn't sent to the parent
                // when it was added.  This probably isn't the right fix, but it seems to work for now.  Note that the same
                // strange "if (X) else if (Y && X)" code is also present in the two previous functions, above.

                if (get_non_inline_ancestor_canvas()) {
                    get_non_inline_ancestor_canvas()->invoke_signal_value_node_child_removed(this, value_node);
                } else {
                    printf("%s:%d == can't get non_inline_ancestor_canvas - parent canvas = %lx\n", __FILE__, __LINE__, uintptr_t(get_parent_canvas().get()));
                }
            }

            break;
        }

    reindex();
}

ValueNode_DynamicList::ValueNode_DynamicList(Type &container_type, Canvas::LooseHandle canvas):
    LinkableValueNode(type_list),
    container_type(&container_type),
    loop_(false)
{
    if (getenv("SYNFIG_DEBUG_SET_PARENT_CANVAS")) {
        printf("%s:%d set parent canvas for dynamic_list %lx to %lx\n", __FILE__, __LINE__, uintptr_t(this), uintptr_t(canvas.get()));
    }

    set_parent_canvas(canvas);
}

ValueNode_DynamicList::ValueNode_DynamicList(Type &container_type, Type &type, Canvas::LooseHandle canvas):
    LinkableValueNode(type),
    container_type(&container_type),
    loop_(false)
{
    if (getenv("SYNFIG_DEBUG_SET_PARENT_CANVAS")) {
        printf("%s:%d set parent canvas for dynamic_list %lx to %lx\n", __FILE__, __LINE__, uintptr_t(this), uintptr_t(canvas.get()));
    }

    set_parent_canvas(canvas);
}

ValueNode_DynamicList::Handle
ValueNode_DynamicList::create_on_canvas(Type &type, Canvas::LooseHandle canvas)
{
    return new ValueNode_DynamicList(type, canvas);
}

ValueNode_DynamicList::~ValueNode_DynamicList()
{
    unlink_all();
}

ValueNode_DynamicList*
ValueNode_DynamicList::create(const ValueBase &value)
{
    vector<ValueBase> value_list(value.get_list());

    vector<ValueBase>::iterator iter;

    if (value_list.empty()) {
        return 0;
    }

    ValueNode_DynamicList* value_node(new ValueNode_DynamicList(value_list.front().get_type()));

    // when creating a list of vectors, start it off being looped.
    // I think the only time this is used if for creating polygons,
    // and we want them to be looped by default
    if (value_node->get_contained_type() == type_vector) {
        value_node->set_loop(true);
    }

    for (iter = value_list.begin(); iter != value_list.end(); ++iter) {
        ValueNode::Handle item(ValueNode_Const::create(*iter));
        value_node->add(ListEntry(item));
        assert(value_node->list.back().value_node);
    }

    return value_node;
}

ValueBase
ValueNode_DynamicList::operator()(Time t)const
{
    if (getenv("SYNFIG_DEBUG_VALUENODE_OPERATORS")) {
        printf("%s:%d operator()\n", __FILE__, __LINE__);
    }

    std::vector<ValueBase> ret_list;
    std::vector<ListEntry>::const_iterator iter;

    assert(container_type);

    for (iter = list.begin(); iter != list.end(); ++iter) {
        bool state(iter->status_at_time(t));

        if (state) {
            if (iter->value_node->get_type() == *container_type) {
                ret_list.push_back((*iter->value_node)(t));
            } else {
                synfig::warning(string("ValueNode_DynamicList::operator()():") + _("List type/item type mismatch, throwing away mismatch"));
            }
        }
    }

    if (list.empty()) {
        synfig::warning(string("ValueNode_DynamicList::operator()():") + _("No entries in list"));
    } else if (ret_list.empty()) {
        synfig::warning(string("ValueNode_DynamicList::operator()():") + _("No entries in ret_list"));
    }

    return ret_list;
}

bool
ValueNode_DynamicList::set_link_vfunc(int i, ValueNode::Handle x)
{
    assert(i >= 0);

    if ((unsigned)i >= list.size()) {
        return false;
    }

    if (x->get_type() != *container_type) {
        return false;
    }

    list[i].value_node = x;
    return true;
}

ValueNode::LooseHandle
ValueNode_DynamicList::get_link_vfunc(int i)const
{
    assert(i >= 0);

    if ((unsigned)i >= list.size()) {
        return 0;
    }

    return list[i].value_node;
}

int
ValueNode_DynamicList::link_count()const
{
    return list.size();
}

String
ValueNode_DynamicList::link_local_name(int i)const
{
    assert(i >= 0 && i < link_count());

    return etl::strprintf(_("Item %03d"), i + 1);
}

ValueNode::Handle
ValueNode_DynamicList::clone(Canvas::LooseHandle canvas, const GUID& deriv_guid)const
{
    {
        ValueNode* x(find_value_node(get_guid()^deriv_guid).get());

        if (x) {
            return x;
        }
    }

    ValueNode_DynamicList* ret = dynamic_cast<ValueNode_DynamicList*>(create_new());
    ret->set_guid(get_guid()^deriv_guid);

    std::vector<ListEntry>::const_iterator iter;

    for (iter = list.begin(); iter != list.end(); ++iter) {
        if (iter->value_node->is_exported()) {
            ret->add(*iter);
        } else {
            ListEntry list_entry(*iter);
            // if(!list_entry.value_node)
            list_entry.value_node = iter->value_node->clone(canvas, deriv_guid);
            ret->add(list_entry);
        }
    }

    ret->set_loop(get_loop());
    ret->set_parent_canvas(canvas);
    return ret;
}

String
ValueNode_DynamicList::link_name(int i)const
{
    return strprintf("item%04d", i);
}

int
ValueNode_DynamicList::get_link_index_from_name(const String &name)const
{
    for (int i = 0; i < link_count(); ++i)
        if (link_name(i) == name) {
            return i;
        }

    throw Exception::BadLinkName(name);
}

bool
ValueNode_DynamicList::check_type(Type &type)
{
    return type == type_list;
}

void
ValueNode_DynamicList::set_member_canvas(etl::loose_handle<Canvas> canvas)
{
    for (vector<ListEntry>::iterator iter = list.begin(); iter != list.end(); iter++) {
        iter->value_node->set_parent_canvas(canvas);
    }
}

Type&
ValueNode_DynamicList::get_contained_type()const
{
    return *container_type;
}

LinkableValueNode*
ValueNode_DynamicList::create_new()const
{
    return new ValueNode_DynamicList(*container_type);
}

int
ValueNode_DynamicList::find_next_valid_entry(int orig_item, Time t)const
{
    int curr_item;

    for (curr_item = orig_item + 1; curr_item != orig_item; curr_item++) {
        if (curr_item == (int)list.size()) {
            curr_item = 0;
            continue;
        }

        if (list[curr_item].status_at_time(t)) {
            return curr_item;
        }
    }

    return curr_item;
}

int
ValueNode_DynamicList::find_prev_valid_entry(int orig_item, Time t)const
{
    int curr_item;

    for (curr_item = orig_item - 1; curr_item != orig_item; curr_item--) {
        if (curr_item == -1) {
            curr_item = list.size();
            continue;
        }

        if (list[curr_item].status_at_time(t)) {
            return curr_item;
        }
    }

    return curr_item;
}

const synfig::Node::time_set	& ValueNode_DynamicList::ListEntry::get_times() const
{
    synfig::ActivepointList::const_iterator 	j = timing_info.begin(),
                                                end = timing_info.end();

    // must remerge with all the other values because we don't know if we've changed...
    times = value_node->get_times();

    for (; j != end; ++j) {
        TimePoint t;
        t.set_time(j->get_time());
        t.set_guid(j->get_guid());

        times.insert(t);
    }

    return times;
}

void ValueNode_DynamicList::get_times_vfunc(Node::time_set &set) const
{
    // add in the active points
    int size = list.size();

    // rebuild all the info...
    for (int i = 0; i < size; ++i) {
        const Node::time_set & tset = list[i].get_times();
        set.insert(tset.begin(), tset.end());
    }
}

// new find functions that don't throw
struct timecmp {
    Time t;

    timecmp(const Time &c) : t(c) {}

    bool operator()(const Activepoint &rhs) const
    {
        return t.is_equal(rhs.get_time());
    }
};

ValueNode_DynamicList::ListEntry::findresult ValueNode_DynamicList::ListEntry::find_uid(const UniqueID& x)
{
    findresult f;
    f.second = false;

    f.first = std::find(timing_info.begin(), timing_info.end(), x);

    if (f.first != timing_info.end()) {
        f.second = true;
    }

    return f;
}

ValueNode_DynamicList::ListEntry::const_findresult ValueNode_DynamicList::ListEntry::find_uid(const UniqueID& x) const
{
    const_findresult f;
    f.second = false;

    f.first = std::find(timing_info.begin(), timing_info.end(), x);

    if (f.first != timing_info.end()) {
        f.second = true;
    }

    return f;
}

ValueNode_DynamicList::ListEntry::findresult ValueNode_DynamicList::ListEntry::find_time(const Time& x)
{
    findresult f;
    f.second = false;

    f.first = std::find_if(timing_info.begin(), timing_info.end(), timecmp(x));

    if (f.first != timing_info.end()) {
        f.second = true;
    }

    return f;
}

ValueNode_DynamicList::ListEntry::const_findresult ValueNode_DynamicList::ListEntry::find_time(const Time& x)const
{
    const_findresult f;
    f.second = false;

    f.first = std::find_if(timing_info.begin(), timing_info.end(), timecmp(x));

    if (f.first != timing_info.end()) {
        f.second = true;
    }

    return f;
}

void
ValueNode_DynamicList::insert_time(const Time& location, const Time& delta)
{
    if (!delta) {
        return;
    }

    std::vector<ListEntry>::iterator iter(list.begin());

    for (; iter != list.end(); ++iter) {
        try {
            ListEntry& item(*iter);

            ActivepointList::iterator iter(item.find_next(location));

            for (; iter != item.timing_info.end(); ++iter) {
                iter->set_time(iter->get_time() + delta);
            }
        } catch (Exception::NotFound) { }
    }

    changed();
}

LinkableValueNode::Vocab
ValueNode_DynamicList::get_children_vocab_vfunc()const
{
    LinkableValueNode::Vocab ret;

    for (unsigned int i = 0; i < list.size(); i++) {
        ret.push_back(ParamDesc(ValueBase(), strprintf("item%04d", i))
                      .set_local_name(etl::strprintf(_("Item %03d"), i + 1))
                     );
    }

    return ret;
}